package com.rainbow.idempotent.lock;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.WriteRedisConnectionException;

import lombok.extern.slf4j.Slf4j;

//分布锁实现,外部如果需要使用，需要@Bean注入到spring容器中
@Slf4j
public class RedissonDistributedLock implements DistributedLock {
	
    private RedissonClient redissonClient;  //非必须
    
    public RedissonDistributedLock(RedissonClient redissonClient){
		this.redissonClient = redissonClient;
	}

	@Override
	public boolean lock(String key) {
		try {
			RLock lock = redissonClient.getLock(key);
	        lock.lock();
	        return true;
		} catch (Exception e) {
			return false;
		}
	}

	@Override
	public boolean tryLock(String key) {
		RLock lock = redissonClient.getLock(key);
        return lock.tryLock();
	}

	@Override
	public boolean tryLock(String key, long expire) {
		RLock lock = redissonClient.getLock(key);
        try {
            return lock.tryLock(WAIT_MILLIS,expire, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            return false;
        }
	}

	@Override
	public boolean tryLock(String key, long expire, long waitTimes) {
		RLock lock = redissonClient.getLock(key);
        try {
            return lock.tryLock(waitTimes, expire, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            return false;
        }
	}

	@Override
	public void releaseLock(String key) {
		RLock lock = redissonClient.getLock(key);
        lock.unlock();
	}

	@Override
	public <T> T lock(String key, long waitTime, long leaseTime, Supplier<T> success, Supplier<T> fail) {
		return doLock(key,waitTime,leaseTime,TimeUnit.MILLISECONDS,success,fail);
	}

	@Override
	public <T> T lock(String key, long leaseTime, Supplier<T> success, Supplier<T> fail) {
		return doLock(key,0,leaseTime,TimeUnit.MILLISECONDS,success,fail);
	}

	@Override
	public <T> T lock(String key, long leaseTime, TimeUnit timeUnit, Supplier<T> success, Supplier<T> fail) {
		return doLock(key,0,leaseTime,timeUnit,success,fail);
	}

	@Override
	public void lock(String key, long waitTime, long leaseTime, Runnable success, Runnable fail) {
		doLock(key,waitTime,leaseTime,TimeUnit.MILLISECONDS,success,fail);
	}

	@Override
	public void lock(String key, long leaseTime, Runnable success, Runnable fail) {
		doLock(key,0,leaseTime,TimeUnit.MILLISECONDS,success,fail);
	}

	@Override
	public void lock(String key, long leaseTime, TimeUnit timeUnit, Runnable success, Runnable fail) {
		doLock(key,0,leaseTime,timeUnit,success,fail);
	}

	private <T> T doLock(String key, long waitTime, long leaseTime, TimeUnit timeUnit, Supplier<T> success, Supplier<T> fail) {
        try {
            RLock lock = null;
            try {
                lock = redissonClient.getLock(key);
            } catch (Exception e) {
                log.error("get Redis Lock Error", e);
                return fail.get();
            }

            boolean tryLock = false;
            try {
                tryLock = lock.tryLock(waitTime, leaseTime, timeUnit);
            } catch (WriteRedisConnectionException e) {
            	log.error("lock tryLock Error", e);
            }

            if (!tryLock) {
                return fail.get();
            }

            try {
                return success.get();
            } catch (Exception e){
                throw e;
            } finally {
                if (lock.getHoldCount() != 0) {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private void doLock(String key, long waitTime, long leaseTime, TimeUnit timeUnit, Runnable success, Runnable fail) {
        try {
            RLock lock = null;
            try {
                lock = redissonClient.getLock(key);
            } catch (Exception e) {
                log.error("get Redis Lock Error", e);
                fail.run();
                return;
            }

            boolean tryLock = false;
            try {
                tryLock = lock.tryLock(waitTime, leaseTime, timeUnit);
            } catch (WriteRedisConnectionException e) {
            	log.error("lock tryLock Error", e);
            	fail.run();
            	return;
            }

            if (!tryLock) {
                fail.run();
                return;
            }

            try {
                success.run();
            } catch (Exception e){
                throw e;
            } finally {
                if (lock.getHoldCount() != 0) {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
